package idp.dashboard;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Vector;

import idp.communication.MessageController;
import idp.communication.MessageListener;
import idp.settings.SettingListener;
import idp.settings.SettingModel;
import idp.utils.DateFormatter;

public class DataController extends Thread implements SettingListener,
		MessageListener {
	/**
	 * PRIVATE VARIABLES
	 */
	private int _interval = 1000;
	
	private long _replayTime, _startTime, _endTime;
	
	private boolean _quit = false, _matchRunning = false;
	 
	private Vector<String[]> _commandBuffer = new Vector<String[]>();

	private Vector<String> _queryBuffer = new Vector<String>();

	private Connection _connection;

	private Object _dbLock = new Object();

	/**
	 * Database Identifiers
	 */
	private int _windSensorDB, _fzeilSensorDB, _gzeilSensorDB,
			_hellingSensorDB, _richtingSensorDB, _gevondenSensorDB,
			_currentMatchDB, _toerenSensorDB, _accuSensorDB;

	private long _windTime = 0, _fzeilTime = 0, _gzeilTime = 0,
			_hellingTime = 0, _richtingTime = 0, _toerenTime;

	/**
	 * Object references
	 */
	
	private static DataController _instance;
	
	/**
	 * Returns the current instance of DataController
	 * @return
	 */
	public static DataController getInstance() {
		if(_instance == null){
			try {
				_instance = new DataController();
				_instance.getMatchReplays();
			} catch (DatabaseErrorException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(!_instance.isAlive()){
			_instance.start();
		}
		return _instance;
	}

	
	
	
	
	/**
	 * Contructor
	 * @throws DatabaseErrorException
	 */
	private DataController() throws DatabaseErrorException {
		super("DataController");
		MessageController.addListener(this);
		if (SettingModel.LoadByKey("UPDATE_INTERVAL") == null) {
			SettingModel.CreateModel("UPDATE_INTERVAL");
			SettingModel.LoadByKey("UPDATE_INTERVAL").setValue(
					String.valueOf(_interval));
		} else {
			_interval = Integer.parseInt(SettingModel.LoadByKey(
					"UPDATE_INTERVAL").getValue());
		}
		SettingModel.LoadByKey("UPDATE_INTERVAL").AddListener(this);
		ConnectToDatabase();
	}

	/**
	 * Executes all given statements at once.
	 * @param statements
	 */
	private void executeStatements(String[] statements) {
		try {
			Statement stat = _connection.createStatement();
			// _connection.setAutoCommit(false);
			for (String s : statements) {
				stat.addBatch(s);
			}
			stat.executeBatch();
			// _connection.commit();
			// _connection.setAutoCommit(true);
			stat.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	/**
	 * Connects to the SQLLite database
	 * @throws DatabaseErrorException
	 */
	private void ConnectToDatabase() throws DatabaseErrorException {
		try {
			Class.forName("org.sqlite.JDBC");
			_connection = DriverManager.getConnection("jdbc:sqlite:log");

			Statement stat = _connection.createStatement();
			stat
					.executeUpdate("CREATE TABLE IF NOT EXISTS Match(id INTEGER PRIMARY KEY, "
							+ "name TEXT, start_time INTEGER, end_time INTEGER)");
			stat
					.executeUpdate("CREATE TABLE IF NOT EXISTS Source(id INTEGER PRIMARY KEY, "
							+ "name TEXT, description TEXT)");
			stat
					.executeUpdate("CREATE TABLE IF NOT EXISTS Entry(id INTEGER PRIMARY KEY, "
							+ "source INTEGER REFERENCES Source(id), match INTEGER REFERENCES Match(id),"
							+ "time INTEGER," + "value INTEGER)");
			stat.close();
			addSources();
			getSourceIds();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			throw new DatabaseErrorException(
					"Kan drivers voor database niet vinden");
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			System.out.println(e);
			throw new DatabaseErrorException(
					"Kan geen verbinding maken met de database");
		}

	}

	/**
	 * Changes the interval
	 */
	public void SettingChanged(SettingModel s) {
		_interval = Integer.parseInt(s.getValue());
	}
	
	
	
	
	/*
	 * 
	 * MAIN DATA LOOP
	 * saves data to the database, or gets it while replaying
	 * 
	 */
	
	/**
	 * Main data loop
	 */
	public void run() {
		while (!_quit) {
			if(_replayTime == 0){
				// Parse sensor data
				if (_queryBuffer.size() > 0) {
					try {
						synchronized (_dbLock) {
							Statement stat = _connection.createStatement();
							for (int i = 0; i < _queryBuffer.size(); i++) {
								stat.addBatch(_queryBuffer.remove(i));
							}
							stat.executeBatch();
							stat.close();
						}
						
						if(_startTime != 0)
							MainFrame.getInstance().dashboard.setTime(DateFormatter.formatInterval(System.currentTimeMillis()-_startTime));
					} catch (SQLException e) {
						System.out.println("A database error!");
					}
				}
				synchronized (this) {
					try {
						wait(500);
					} catch (InterruptedException e) {
					}
				}
			}
			else{
				if(_endTime < _replayTime)
				{
					// Replay has ended, stop replaying
					stopMatchReplay();
					continue;
				}
				
				doReplayLoop(_replayTime);
				_replayTime+=1000;
				MainFrame.getInstance().dashboard.setTime(DateFormatter.formatInterval(_replayTime-_startTime));
				
				try {
					Thread.sleep(1000l);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			} 
		}
	}

	/**
	 * Stops the thread
	 */
	public void quit() {
		_quit = true;
	}
	
	
	
	
	
	
	/*
	 * 
	 *     MATCH CONTROLL
	 * 
	 */
	
	
	/**
	 * Updates all UI-sensors with DB-values at a given time
	 * @param replayTime the current time
	 */
	private void doReplayLoop(long replayTime)
	{
		try {
			Statement stat = _connection.createStatement();
			String query = "SELECT * FROM Entry WHERE time > '"+(replayTime-1000)+"' AND time <= '"+replayTime+"' GROUP BY source";
			ResultSet rs = stat.executeQuery(query);
			
			while(rs.next())
			{
				if(rs.getInt(2) == _windSensorDB){
					MainFrame.getInstance().dashboard.WindMessage(rs.getInt(5));
					MainFrame.getInstance().dashboard.WindMessage(rs.getInt(5));
					MainFrame.getInstance().dashboard.WindMessage(rs.getInt(5));
				}
				else if(rs.getInt(2) == _richtingSensorDB){
					MainFrame.getInstance().dashboard.RichtingMessage(rs.getInt(5));
					MainFrame.getInstance().dashboard.RichtingMessage(rs.getInt(5));
					MainFrame.getInstance().dashboard.RichtingMessage(rs.getInt(5));
				}
				else if(rs.getInt(2) == _hellingSensorDB){
					MainFrame.getInstance().dashboard.HellingMessage(rs.getInt(5));
					MainFrame.getInstance().dashboard.HellingMessage(rs.getInt(5));
					MainFrame.getInstance().dashboard.HellingMessage(rs.getInt(5));
				}
				else if(rs.getInt(2) == _gzeilSensorDB){
					MainFrame.getInstance().dashboard.GrootZeilMessage(rs.getInt(5));
					MainFrame.getInstance().dashboard.GrootZeilMessage(rs.getInt(5));
					MainFrame.getInstance().dashboard.GrootZeilMessage(rs.getInt(5));
				}
				else if(rs.getInt(2) == _fzeilSensorDB){
					MainFrame.getInstance().dashboard.FokZeilMessage(rs.getInt(5));
					MainFrame.getInstance().dashboard.FokZeilMessage(rs.getInt(5));
					MainFrame.getInstance().dashboard.FokZeilMessage(rs.getInt(5));
				}
				else if(rs.getInt(2) == _gevondenSensorDB){
					MainFrame.getInstance().dashboard.rfidTagMessage(rs.getInt("time"), rs.getInt(5));
				}
			}
			
			stat.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Starts a blackback
	 * @param name The name stored in the database
	 */
	public void startMatchReplay(String name){
		try {
			Statement stat = _connection.createStatement();
			ResultSet rs = stat.executeQuery("SELECT start_time,end_time FROM Match WHERE name='"+name+"'");
			_startTime = rs.getLong(1);
			_replayTime = rs.getLong(1);
			_endTime = rs.getLong(2);
			System.out.println(_startTime+" "+_endTime);
			stat.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Stops a blackback
	 */
	public void stopMatchReplay()
	{
		_startTime = 0;
		MainFrame.getInstance().recordButton.setEnabled(true);
		MainFrame.getInstance().replayButton.setText("Start Terugblik");
		MainFrame.getInstance().dashboard.setTime("");
		_replayTime = 0;
	}
	
	/**
	 * Adds all saved replays to the UI
	 */
	public void getMatchReplays(){
		try {
			Statement stat = _connection.createStatement();
			ResultSet rs = stat.executeQuery("SELECT name FROM Match ORDER BY start_time ASC");
			
			while(rs.next())
			{
				MainFrame.getInstance().matches.add(rs.getString("name"));
			}
			stat.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Deletes a match from the database (does not delete entries)
	 * @param name The name stored in the database
	 */
	public void deleteMatch(String name)
	{
		try {
			Statement stat = _connection.createStatement();
			stat.executeUpdate("DELETE FROM Match WHERE name ='"+name+"'");
			stat.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Returns the DB-identifier of the current recording
	 * @return
	 */
	public int getMatchID()
	{
		return _currentMatchDB;
	}

	/**
	 * Start a match recording
	 * @param name
	 */
	public void startMatchRecording(String name) {
		Statement stat;
		try {
			synchronized (_dbLock) {
				stat = _connection.createStatement();
				stat
						.executeUpdate("INSERT INTO Match(name, start_time) VALUES ('"
								+ name
								+ "', "
								+ System.currentTimeMillis()
								+ ")");
				_currentMatchDB = (stat
						.executeQuery("SELECT last_insert_rowid() id FROM Match")
						.getInt("id"));
				System.out.println("current matchid: " + _currentMatchDB);
				stat.close();
				
				_startTime = System.currentTimeMillis();
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		_matchRunning = true;
	}

	/**
	 * Stop a match recording
	 * @throws SQLException
	 */
	public void stopMatchRecording() {
		_matchRunning = false;
		try {
			synchronized (_dbLock) {
				Statement stat = _connection.createStatement();
				stat.executeUpdate("UPDATE Match SET end_time = "
						+ System.currentTimeMillis() + " WHERE id = "
						+ _currentMatchDB + "");
				stat.close();
				
				System.out.println("Stopped match");
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	
	
	
	
	/*
	 * 
	 *    SENSOR CONTROL
	 * 
	 */
	
	/**
	 * Adds the default sensor entries to the DB
	 */
	private void addSources() throws SQLException {
		Statement stat = _connection.createStatement();
		ResultSet rs = stat.executeQuery("SELECT * FROM Source");
		if (!rs.next()) {
			String[] queries = {
					"INSERT INTO Source(name, description) VALUES ('Wind Sensor', 'Geeft de wind richting')",
					"INSERT INTO Source(name, description) VALUES ('Fok Sensor', 'Geeft de stand van de fok')",
					"INSERT INTO Source(name, description) VALUES ('Zeil Sensor', 'Geeft de stand van het grote zel')",
					"INSERT INTO Source(name, description) VALUES ('Helling Sensor', 'Geeft de helling van de boot')",
					"INSERT INTO Source(name, description) VALUES ('Richting Sensor', 'Geeft de richting van de boot')",
					"INSERT INTO Source(name, description) VALUES ('rfid Sensor', 'Geeft de id van de gelezen rfid tags')",
					"INSERT INTO Source(name, description) VALUES ('toeren Sensor', 'geeft toerental van de motor')",
					"INSERT INTO Source(name, description) VALUES ('accu Sensor', 'Geeft een waarschuwing als de accu bijna leeg is')" };
			executeStatements(queries);
		}
		rs.close();
		stat.close();
	}
	
	/**
	 * Saves all DB-identifiers of sensor entries in memory
	 * @throws SQLException
	 */
	private void getSourceIds() throws SQLException {
		Statement stat = _connection.createStatement();
		_windSensorDB = (stat
				.executeQuery("SELECT id FROM Source WHERE name='Wind Sensor'")
				.getInt("id"));
		_fzeilSensorDB = (stat
				.executeQuery("SELECT id FROM Source WHERE name='Fok Sensor'")
				.getInt("id"));
		_gzeilSensorDB = (stat
				.executeQuery("SELECT id FROM Source WHERE name='Zeil Sensor'")
				.getInt("id"));
		_hellingSensorDB = (stat
				.executeQuery("SELECT id FROM Source WHERE name='Helling Sensor'")
				.getInt("id"));
		_richtingSensorDB = (stat
				.executeQuery("SELECT id FROM Source WHERE name='Richting Sensor'")
				.getInt("id"));
		_gevondenSensorDB = (stat
				.executeQuery("SELECT id FROM Source WHERE name='rfid Sensor'")
				.getInt("id"));
		_accuSensorDB = (stat
				.executeQuery("SELECT id FROM Source WHERE name='accu Sensor'")
				.getInt("id"));
		_toerenSensorDB = (stat
				.executeQuery("SELECT id FROM Source WHERE name='toeren Sensor'")
				.getInt("id"));
		stat.close();
	}

	/**
	 * Checks if the user defined interval has been passed
	 * @param item The last time
	 * @return True, if the interval has been passed
	 */
	private boolean needsUpdate(long item) {
		if ((System.currentTimeMillis() - item) > _interval) {
			item = System.currentTimeMillis();
			return true;
		} else {
			return false;
		}
	}

	public void AccuMessage(int value) {
		if (_matchRunning) {
			_queryBuffer
					.add("INSERT INTO Entry(Source, match, time, value) VALUES ("
							+ _accuSensorDB
							+ ", "
							+ _currentMatchDB
							+ ", "
							+ System.currentTimeMillis() + ", " + value + ")");
		}

	}

	public void FokZeilMessage(int value) {
		if (_matchRunning && needsUpdate(_fzeilTime)) {
			_queryBuffer
					.add("INSERT INTO Entry(source, match, time, value) VALUES("
							+ _fzeilSensorDB
							+ ", "
							+ _currentMatchDB
							+ ", "
							+ System.currentTimeMillis() + ", " + value + ")");
		}

	}

	public void GrootZeilMessage(int value) {
		if (_matchRunning && needsUpdate(_gzeilTime)) {
			_queryBuffer
					.add("INSERT INTO Entry(source, match, time, value) VALUES("
							+ _gzeilSensorDB
							+ ", "
							+ _currentMatchDB
							+ ", "
							+ System.currentTimeMillis() + ", " + value + ")");
		}

	}

	public void HellingMessage(int value) {
		if (_matchRunning && needsUpdate(_hellingTime)) {
			_queryBuffer
					.add("INSERT INTO Entry(source, match, time, value) VALUES("
							+ _hellingSensorDB
							+ ", "
							+ _currentMatchDB
							+ ", "
							+ System.currentTimeMillis() + ", " + value + ")");
		}

	}

	public void RichtingMessage(int value) {
		if (_matchRunning && needsUpdate(_richtingTime)) {
			_queryBuffer
					.add("INSERT INTO Entry(source, match, time, value) VALUES("
							+ _richtingSensorDB
							+ ", "
							+ _currentMatchDB
							+ ", "
							+ System.currentTimeMillis() + ", " + value + ")");
		}

	}

	public void ToerenMessage(int value) {
		if (_matchRunning && needsUpdate(_toerenTime)) {
			_queryBuffer
					.add("INSERT INTO Entry(Source, match, time, value) VALUES("
							+ _toerenSensorDB
							+ ", "
							+ _currentMatchDB
							+ ", "
							+ System.currentTimeMillis() + ", " + value + ")");
		}

	}

	public void WindMessage(int value) {
		if (_matchRunning && needsUpdate(_windTime)) {
			_queryBuffer
					.add("INSERT INTO Entry(source, match, time, value) VALUES("
							+ _windSensorDB
							+ ", "
							+ _currentMatchDB
							+ ", "
							+ System.currentTimeMillis() + ", " + value + ")");
		}

	}

	public void rfidTagMessage(long time, int tag) {
		if (_matchRunning) {
			_queryBuffer
					.add("INSERT INTO Entry(source, match, time, value) VALUES("
							+ _gevondenSensorDB
							+ ", "
							+ _currentMatchDB
							+ ", "
							+ time
							+ ", "
							+ tag + ")");
		}

	}
}
